/**************************************************************************************
*  SpiMatrix.java
*  Program demonstrating the use of SPI with LPC21xx to control 8x8 LED matrix.
**************************************************************************************
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY,  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL RTJ COMPUTING BE LIABLE FOR ANY CLAIM, DAMAGES
* OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
* Created by:
*      RTJ Computing
**************************************************************************************/
import lpc2xxx.comm.*;
import lpc2xxx.hw.*;
import lpc2xxx.edu.*;
import javax.events.*;


class SpiMatrix extends Thread
{
    static byte[][] Patterns = 
    { 
        // SPACE
        { (byte)0xFF, (byte)0x7F, (byte)0xFF, (byte)0xBF, (byte)0xFF, (byte)0xDF, (byte)0xFF, (byte)0xEF, 
          (byte)0xFF, (byte)0xF7, (byte)0xFF, (byte)0xFB, (byte)0xFF, (byte)0xFD, (byte)0xFF, (byte)0xFE },
        // A
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xDD, (byte)0xF7, (byte)0xC1, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xDD, (byte)0xFE },
        // B
        { (byte)0xFF, (byte)0x7F, (byte)0xE1, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xE1, (byte)0xF7, (byte)0xDD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE1, (byte)0xFE },
        // C
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xFD, (byte)0xEF, 
          (byte)0xFD, (byte)0xF7, (byte)0xFD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // D
        { (byte)0xFF, (byte)0x7F, (byte)0xE1, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xDD, (byte)0xF7, (byte)0xDD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE1, (byte)0xFE },
        // E
        { (byte)0xFF, (byte)0x7F, (byte)0xC1, (byte)0xBF, (byte)0xFD, (byte)0xDF, (byte)0xFD, (byte)0xEF, 
          (byte)0xF1, (byte)0xF7, (byte)0xFD, (byte)0xFB, (byte)0xFD, (byte)0xFD, (byte)0xC1, (byte)0xFE },
        // F
        { (byte)0xFF, (byte)0x7F, (byte)0xC1, (byte)0xBF, (byte)0xFD, (byte)0xDF, (byte)0xFD, (byte)0xEF, 
          (byte)0xF1, (byte)0xF7, (byte)0xFD, (byte)0xFB, (byte)0xFD, (byte)0xFD, (byte)0xFD, (byte)0xFE },
        // G
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xFD, (byte)0xEF, 
          (byte)0xFD, (byte)0xF7, (byte)0xCD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xC3, (byte)0xFE },
        // H
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xC1, (byte)0xF7, (byte)0xDD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xDD, (byte)0xFE },
        // I
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xF7, (byte)0xDF, (byte)0xF7, (byte)0xEF, 
          (byte)0xF7, (byte)0xF7, (byte)0xF7, (byte)0xFB, (byte)0xF7, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // J
        { (byte)0xFF, (byte)0x7F, (byte)0xC7, (byte)0xBF, (byte)0xDF, (byte)0xDF, (byte)0xDF, (byte)0xEF, 
          (byte)0xDF, (byte)0xF7, (byte)0xDF, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // K
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xED, (byte)0xDF, (byte)0xF5, (byte)0xEF, 
          (byte)0xF9, (byte)0xF7, (byte)0xF5, (byte)0xFB, (byte)0xED, (byte)0xFD, (byte)0xDD, (byte)0xFE },
        // L
        { (byte)0xFF, (byte)0x7F, (byte)0xFD, (byte)0xBF, (byte)0xFD, (byte)0xDF, (byte)0xFD, (byte)0xEF, 
          (byte)0xFD, (byte)0xF7, (byte)0xFD, (byte)0xFB, (byte)0xFD, (byte)0xFD, (byte)0xC1, (byte)0xFE },
        // M
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xC9, (byte)0xDF, (byte)0xD5, (byte)0xEF, 
          (byte)0xDD, (byte)0xF7, (byte)0xDD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xDD, (byte)0xFE },
        // N
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xD9, (byte)0xEF, 
          (byte)0xD5, (byte)0xF7, (byte)0xCD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xDD, (byte)0xFE },
        // O
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xDD, (byte)0xF7, (byte)0xDD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // P
        { (byte)0xFF, (byte)0x7F, (byte)0xE1, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xE1, (byte)0xF7, (byte)0xFD, (byte)0xFB, (byte)0xFD, (byte)0xFD, (byte)0xFD, (byte)0xFE },
        // Q
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xDD, (byte)0xF7, (byte)0xD5, (byte)0xFB, (byte)0xED, (byte)0xFD, (byte)0xD3, (byte)0xFE },
        // R
        { (byte)0xFF, (byte)0x7F, (byte)0xE1, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xE1, (byte)0xF7, (byte)0xF5, (byte)0xFB, (byte)0xED, (byte)0xFD, (byte)0xDD, (byte)0xFE },
        // S
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xFD, (byte)0xEF, 
          (byte)0xE3, (byte)0xF7, (byte)0xDF, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // T
        { (byte)0xFF, (byte)0x7F, (byte)0xC1, (byte)0xBF, (byte)0xF7, (byte)0xDF, (byte)0xF7, (byte)0xEF, 
          (byte)0xF7, (byte)0xF7, (byte)0xF7, (byte)0xFB, (byte)0xF7, (byte)0xFD, (byte)0xF7, (byte)0xFE },
        // U
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xDD, (byte)0xF7, (byte)0xDD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // V
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xEB, (byte)0xEF, 
          (byte)0xEB, (byte)0xF7, (byte)0xEB, (byte)0xFB, (byte)0xF7, (byte)0xFD, (byte)0xF7, (byte)0xFE },
        // W
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xDD, (byte)0xF7, (byte)0xD5, (byte)0xFB, (byte)0xD5, (byte)0xFD, (byte)0xEB, (byte)0xFE },
        // X
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xEB, (byte)0xEF, 
          (byte)0xF7, (byte)0xF7, (byte)0xEB, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xDD, (byte)0xFE },
        // Y
        { (byte)0xFF, (byte)0x7F, (byte)0xDD, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xE3, (byte)0xF7, (byte)0xF7, (byte)0xFB, (byte)0xF7, (byte)0xFD, (byte)0xF7, (byte)0xFE },
        // Z
        { (byte)0xFF, (byte)0x7F, (byte)0xC1, (byte)0xBF, (byte)0xDF, (byte)0xDF, (byte)0xEF, (byte)0xEF, 
          (byte)0xF7, (byte)0xF7, (byte)0xFB, (byte)0xFB, (byte)0xFD, (byte)0xFD, (byte)0xC1, (byte)0xFE },
        // 0
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xCD, (byte)0xEF, 
          (byte)0xD5, (byte)0xF7, (byte)0xD9, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // 1
        { (byte)0xFF, (byte)0x7F, (byte)0xF7, (byte)0xBF, (byte)0xF3, (byte)0xDF, (byte)0xF7, (byte)0xEF, 
          (byte)0xF7, (byte)0xF7, (byte)0xF7, (byte)0xFB, (byte)0xF7, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // 2
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDF, (byte)0xEF, 
          (byte)0xE7, (byte)0xF7, (byte)0xFB, (byte)0xFB, (byte)0xFD, (byte)0xFD, (byte)0xC1, (byte)0xFE },
        // 3
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDF, (byte)0xEF, 
          (byte)0xEF, (byte)0xF7, (byte)0xDF, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // 4
        { (byte)0xFF, (byte)0x7F, (byte)0xEF, (byte)0xBF, (byte)0xF7, (byte)0xDF, (byte)0xFB, (byte)0xEF, 
          (byte)0xED, (byte)0xF7, (byte)0xC1, (byte)0xFB, (byte)0xEF, (byte)0xFD, (byte)0xEF, (byte)0xFE },
        // 5
        { (byte)0xFF, (byte)0x7F, (byte)0xC1, (byte)0xBF, (byte)0xFD, (byte)0xDF, (byte)0xE1, (byte)0xEF, 
          (byte)0xDF, (byte)0xF7, (byte)0xDF, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // 6
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xFD, (byte)0xEF, 
          (byte)0xE1, (byte)0xF7, (byte)0xDD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // 7
        { (byte)0xFF, (byte)0x7F, (byte)0xC1, (byte)0xBF, (byte)0xDF, (byte)0xDF, (byte)0xDF, (byte)0xEF, 
          (byte)0xEF, (byte)0xF7, (byte)0xF7, (byte)0xFB, (byte)0xFB, (byte)0xFD, (byte)0xFD, (byte)0xFE },
        // 8
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xE3, (byte)0xF7, (byte)0xDD, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE },
        // 9
        { (byte)0xFF, (byte)0x7F, (byte)0xE3, (byte)0xBF, (byte)0xDD, (byte)0xDF, (byte)0xDD, (byte)0xEF, 
          (byte)0xC3, (byte)0xF7, (byte)0xDF, (byte)0xFB, (byte)0xDD, (byte)0xFD, (byte)0xE3, (byte)0xFE }
    };

    static Switch sw = new Switch();
    static boolean scroll = false;
    static int scrollSpeed = 100;
    static byte shift = 0;
    static int textIdx = 0;
    static int patIdx = 0;
    static int nextIdx = patIdx + 1;
    static StringBuffer text;
    static char scrollDir = '1';

    static void main(String[] args) 
    {
        Console.println("");
        Console.println("LPC214x SPI LED matrix demo");
        Console.println("This program is for use with LPC2148 Education board");
        Console.println("Jumper settings:");
        Console.println("  disconnect J42");
        Console.println("  connect    J24,J33,J37");
        Console.println("\r\nUse joystick left/right to change scrolling speed");
        
        SpiMatrix sm = new SpiMatrix();
        sm.start();

        Events ev = new Events();
        Timer tmr = new Timer();
        IncremetTimer incHandler = new IncremetTimer();
        try 
        {
            tmr.addHandler(incHandler);
            ev.addEvent(tmr, 0);
            ev.start();
        }
        catch (Exception e)
        {
            Console.println("Events exception " + e.getMessage());
            return;
        }

        clear();
        sw.start();
        showMenu();

        boolean waitRelease = false;
        while (true)
        {
            char key = Console.getKey();
            if (key == Console.ESC)
                break;

            if (waitRelease)
            {
                if (!sw.left() && !sw.right())
                    waitRelease = false;
            }
            else 
            {
                int newScrollSpeed = scrollSpeed;
                if (sw.left() && scrollSpeed > 30)
                    newScrollSpeed -= 10;
                else if (sw.right() && scrollSpeed < 200)
                    newScrollSpeed += 10;
                if (newScrollSpeed != scrollSpeed)
                {
                    scrollSpeed = newScrollSpeed;
                    tmr.start(incHandler, scrollSpeed);
                    waitRelease = true;
                }
            }

            switch (key)
            {
                case '1':
                    text = new StringBuffer(Patterns.length);
                    text.setLength(Patterns.length);
                    for (int i=0; i < text.length(); i++)
                        text.setCharAt(i, (char)i);
                    textIdx = 0;
                    patIdx = text.charAt(0);
                    nextIdx = text.charAt(1);
                    scroll = true;
                    tmr.start(incHandler, scrollSpeed);
                    break;

                case '2':
                    scroll = false;
                    delay(100);
                    clear();
                    Console.print("Enter text (0-9,A-Z chars): ");
                    String s = Console.getKeys();
                    if (s != null && s.length() > 0)
                    {
                        text = new StringBuffer(s);
                        for (int i=0; i < text.length(); i++)
                        {
                            char ch = text.charAt(i);
                            if (ch >= 'A' && ch <= 'Z')
                                ch -= 0x40;
                            else if (ch >= 'a' && ch <= 'z')
                                ch -= 0x60;
                            else if (ch >= '0' && ch <= '9')
                                ch = (char)((ch - 0x30) + ('Z' - 0x40) + 1);
                            else
                                ch = 0;
                            text.setCharAt(i, ch);
                        }
                        text.append('\0');
                        textIdx = 0;
                        patIdx = text.charAt(0);
                        nextIdx = text.charAt(1);
                        scroll = true;
                        scrollSpeed = 100;
                        tmr.start(incHandler, scrollSpeed);
                    }
                    showMenu();
                    break;

                case '3':
                    scroll = false;
                    delay(100);
                    clear();
                    Console.print("Select scrolling direction (1=right->left, 2=bottom->up): ");
                    while (true)
                    {
                        char ch = Console.getKey();
                        if (ch == Console.ESC)
                            break;

                        if (ch == '1' || ch == '2')
                        {
                            scrollDir = ch;
                            Console.putch(ch);
                            break;
                        }
                    }
                    showMenu();
                    if (text != null && text.length() > 0)
                    {
                        textIdx = 0;
                        patIdx = text.charAt(0);
                        nextIdx = text.charAt(1);
                        scroll = true;
                        scrollSpeed = 100;
                        tmr.start(incHandler, scrollSpeed);
                    }
                    break;
            }
        }
        scroll = false;
        delay(100);
        clear();
    }

    static void delay(int ms)
    {
        try
        {
            sleep(ms);
        }
        catch (InterruptedException e) {}
    }

    static void clear()
    {
        byte[] clr = { (byte)0xFF, (byte)0xFF };
        SPI.write(clr, 0, 2);
        SPI.waitReady();
    }

    static void showMenu()
    {
        Console.println("");
        Console.println("Press:");
        Console.println("   1   - scroll alphabet");
        Console.println("   2   - scroll text");
        Console.println("   3   - set scroll direction");
        Console.println("   ESC - exit program");
    }

    SpiMatrix()
    {
        setDaemon(true);
    }

    public void run()
    {
        byte[] pat = new byte[2];
        
        SPI.init(GPIO.P15);
        while (true)
        {
            if (!scroll)
                continue;

            for (int i=0; i < 16; i += 2)
            {
                if (scrollDir == '1')
                {
                    int row = (Patterns[patIdx][i] & 0xFF) >> shift;
                    row |= (Patterns[nextIdx][i] << (8 - shift));
                    pat[0] = (byte)row;
                    pat[1] = Patterns[patIdx][i+1];
                }
                else
                {
                    int offs = i + (shift << 1);
                    if (offs < 16)
                        pat[0] = Patterns[patIdx][offs];
                    else
                        pat[0] = Patterns[nextIdx][offs - 16];
                    pat[1] = Patterns[patIdx][i+1];
                }
                SPI.write(pat, 0, 2);
                SPI.waitReady();
                delay(3);
            }
        }
    }

    static void scrollNext()
    {
        if (++shift == 8)
        {
            shift = 0;
            if (++textIdx == text.length())
                textIdx = 0;
            patIdx = text.charAt(textIdx);
            nextIdx = (textIdx + 1) == text.length() ? text.charAt(0) : text.charAt(textIdx + 1);
        }
    }
}

class IncremetTimer extends PeriodicTimerHandler
{
    IncremetTimer()
    {
    }

    public void handleAsyncEvent()
    {
        SpiMatrix.scrollNext();
    }
}
